package org.filecollector.filecontroller.cataloguemap;

import org.filecollector.filecontroller.virtualcatalogue.VirtualCatalogue;
import org.filecollector.filecontroller.virtualcatalogue.groupname.GroupName;

import java.util.*;

/**
 * Class extending Catalogue Map.
 * <p>
 *     Class contains {@link LinkedHashMap} as inner field and redirect most of methods to it
 * </p>
 * <p>
 *     Class constructed that way to provide work both with GroupName and Id
 * </p>
 */
public class CatalogueMapClass extends CatalogueMap {

    LinkedHashMap<GroupName, VirtualCatalogue> innerMap;

    /**
     * Constructor an empty insertion-ordered {@link LinkedHashMap} instance with the specified
     * initial capacity and load factor.
     * @param initialCapacity the initial capacity
     * @param loadFactor the load factor
     * @throws IllegalArgumentException – if the initial capacity is negative or the load factor is nonpositive
     * @apiNote To create a LinkedHashMap with an initial capacity that accommodates an expected number of mappings, use newLinkedHashMap.
     */
    public CatalogueMapClass(int initialCapacity, float loadFactor) {
        innerMap = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

    /**
     * Constructs an empty insertion-ordered {@link LinkedHashMap} instance
     * with the specified initial capacity and a default load factor (0.75).
     * @param initialCapacity the initial capacity
     * @throws IllegalArgumentException – if the initial capacity is negative
     * @apiNote To create a LinkedHashMap with an initial capacity that accommodates an expected number of mappings, use newLinkedHashMap.     * @param initialCapacity
     */
    public CatalogueMapClass(int initialCapacity) {
        innerMap = new LinkedHashMap<>(initialCapacity);
    }

    /**
     * Constructs an empty insertion-ordered {@link LinkedHashMap} instance with
     * the default initial capacity (16) and load factor (0.75).
     */
    public CatalogueMapClass() {
        innerMap = new LinkedHashMap<>();
    }

    /**
     * Constructs an insertion-ordered {@link LinkedHashMap} instance with the same
     * mappings as the specified map. The {@link LinkedHashMap} instance is created with
     * a default load factor (0.75) and an initial capacity sufficient to hold the mappings in the specified map.
     * @param m the map whose mappings are to be placed in this map
     * @throws NullPointerException if the specified map is null
     */
    public CatalogueMapClass(Map<? extends GroupName, ? extends VirtualCatalogue> m) {
        innerMap = new LinkedHashMap<>(m);
    }

    /**
     * Constructs an empty {@link LinkedHashMap} instance with the specified initial capacity,
     * load factor and ordering mode.
     * @param initialCapacity the initial capacity loadFactor – the load factor accessOrder – the ordering
     *                        mode - true for access-order, false for insertion-order
     * @throws IllegalArgumentException – if the initial capacity is negative or the load factor is nonpositive
     */
    public CatalogueMapClass(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        innerMap = new LinkedHashMap<>(initialCapacity, loadFactor, accessOrder);
    }

    /**
     * @return size
     */
    @Override
    public int size() {
        return innerMap.size();
    }

    /**
     * @return true if innerMap is empty
     */
    @Override
    public boolean isEmpty() {
        return innerMap.isEmpty();
    }

    /**
     * @param key key whose presence in this map is to be tested. Can be Long.
     * @return <li>true if key is Long and matches to any GroupName</li>
     * <li>true if key is GroupName and none of existing ones in innerMap not intersected</li>
     * <li>false in other cases</li>
     */
    @Override
    public boolean containsKey(Object key) {
        if (key instanceof GroupName) {
            return innerMap.containsKey(key) && innerMap.keySet().stream().noneMatch(a -> a.isIntersected(a));
        }
        if (key instanceof Long) {
            return innerMap.keySet().stream().anyMatch(a -> a.matchesId((Long) key));
        }
        return false;
    }

    /**
     * @param value value whose presence in this map is to be tested
     * @return true if innerMap contains value
     */
    @Override
    public boolean containsValue(Object value) {
        return innerMap.containsValue(value);
    }

    /**
     * @param key the key whose associated value is to be returned
     * @return <li>if key is Long returns catalogue that can contains this id</li>
     * <li>In other cases default behaviour</li>
     */
    @Override
    public VirtualCatalogue get(Object key) {
        if (key instanceof Long) {
            return innerMap.entrySet().stream()
                    .filter(a -> a.getKey().matchesId((Long) key))
                    .findAny()
                    .orElseThrow()
                    .getValue();
        }
        return innerMap.get(key);
    }

    /**
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return <li>null if key intersects with any other key in map</li>
     * <li>default result of LinkedHashMap::put in other cases</li>
     */
    @Override
    public VirtualCatalogue put(GroupName key, VirtualCatalogue value) {
        if (innerMap.keySet().stream().anyMatch(a -> a.isIntersected(key))) {
            return null;
        }
        return innerMap.put(key, value);
    }

    /**
     * @param key key whose mapping is to be removed from the map
     * @return <li>if key is Long, remove GroupName that could contain this id</li>
     * <li>default behaviour in other cases</li>
     */
    @Override
    public VirtualCatalogue remove(Object key) {
        if (key instanceof Long) {
            var rem = innerMap.entrySet().stream()
                    .filter(a -> a.getKey().matchesId((Long) key))
                    .findAny()
                    .orElse(null);
            if (rem == null) {
                return null;
            }
            return remove(rem.getKey());
        }
        return innerMap.remove(key);

    }

    /**
     * @param m mappings to be stored in this map
     * @apiNote do nothing if any key of m intersected to any key of innerMap
     */
    @Override
    public void putAll(Map<? extends GroupName, ? extends VirtualCatalogue> m) {
        if (innerMap.keySet().stream()
                .anyMatch(
                        a -> m.keySet().stream().anyMatch(a::isIntersected)
                )
        ) {
            return;
        }
        innerMap.putAll(m);
    }

    /**
     * Clear damn innerMap
     */
    @Override
    public void clear() {
        innerMap.clear();
    }

    /**
     * @return set of map's keys
     */
    @Override
    public Set<GroupName> keySet() {
        return innerMap.keySet();
    }

    /**
     * @return set of map's values
     */
    @Override
    public Collection<VirtualCatalogue> values() {
        return innerMap.values();
    }

    /**
     * @return set of map's entries
     */
    @Override
    public Set<Entry<GroupName, VirtualCatalogue>> entrySet() {
        return innerMap.entrySet();
    }

    /**
     * @param key the key whose associated value is to be returned
     * @param defaultValue the default mapping of the key
     * @return <li>if key is Long returns value associated to key or default;</li>
     * <li>standard behaviour in other cases</li>
     */
    @Override
    public VirtualCatalogue getOrDefault(Object key, VirtualCatalogue defaultValue) {
        if (key instanceof Long) {
            var trueKey = innerMap.keySet().stream().filter(a -> a.matchesId((Long) key)).findAny();
            if (trueKey.isEmpty()) {
                return defaultValue;
            }
            return getOrDefault(trueKey, defaultValue);
        }
        return innerMap.getOrDefault(key, defaultValue);
    }

    /**
     * @param key the key whose associated value is to be returned
     * @param value the value
     * @return <li>if key is Long finds associated key and remove by default way;</li>
     * <li>standard behaviour in other cases</li>
     */
    @Override
    public boolean remove(Object key, Object value) {
        if (key instanceof Long) {
            var trueKey = innerMap.keySet().stream().filter(a -> a.matchesId((Long) key)).findAny();
            if (trueKey.isEmpty()) {
                return false;
            }
            return remove(trueKey, value);
        }
        return innerMap.remove(key, value);
    }
}
